﻿using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;

namespace Fadd.Net
{
	/// <summary>
	/// Send or receive file(s).
	/// </summary>
	/// <remarks>
	/// Binary format: [headersize][BinaryFormatted header class][data]
	/// </remarks>
	internal class FileTransfer
	{
		private readonly byte[] _inbuffer = new byte[65535];
		private readonly Socket _socket;

		public FileTransfer(Socket socket)
		{
			_socket = socket;
		}

		public FileTransfer()
		{
		}

		/// <summary>
		/// Read file 
		/// </summary>
		/// <param name="path">Where to save the file</param>
		/// <remarks>
		/// Filename is transfered over the socket. 
		/// </remarks>
		public void ReadFile(string path)
		{
			ReadBytes(8);
			int length = BitConverter.ToInt32(_inbuffer, 0);
			ReadBytes(length);

			FileHeader header;

			using (var stream = new MemoryStream())
			{
				stream.Write(_inbuffer, 0, length);
				stream.Flush();
				stream.Position = 0;

				var formatter = new BinaryFormatter();
				header = (FileHeader) formatter.Deserialize(stream);
			}

			WriteToFile(header);
			FileReceived(this, new FileEventArgs(header.FileName));
		}


		/// <summary>
		/// A file have been received.
		/// </summary>
		public event EventHandler<FileEventArgs> FileReceived = delegate { };



		private void WriteToFile(FileHeader header)
		{
			int bytesLeft = header.Size;

			using (var stream = new FileStream(header.FileName, FileMode.Create, FileAccess.Write, FileShare.Read))
			{
				int bytesRead = _socket.Receive(_inbuffer);
				while (bytesLeft > 0)
				{
					stream.Write(_inbuffer, 0, bytesRead);
					bytesLeft -= bytesRead;
					bytesRead = _socket.Receive(_inbuffer, 0, Math.Min(_inbuffer.Length, bytesLeft), SocketFlags.None);
				}
			}
		}

		private void ReadBytes(int count)
		{
			int bytesRead = _socket.Receive(_inbuffer, 0, 8, SocketFlags.None);
			while (bytesRead < count)
				bytesRead += _socket.Receive(_inbuffer, bytesRead, count - bytesRead, SocketFlags.None);
		}


		public static void Send(IPEndPoint endPoint, string fileName)
		{
			var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			socket.Connect(endPoint);
			var transfer = new FileTransfer(socket);
			transfer.Send(fileName);
		}

		public void Send(string fileName)
		{
			var fi = new FileInfo(fileName);
			var formatter = new BinaryFormatter();
			var headerStream = new MemoryStream();
			var header = new FileHeader {FileName = fi.Name + "." + fi.Extension, Size = (int) fi.Length, ModifiedAt = fi.LastWriteTimeUtc};
			formatter.Serialize(headerStream, header);
			headerStream.Flush();
			headerStream.Position = 0;

			_socket.Send(BitConverter.GetBytes((Int32) headerStream.Length));
			_socket.Send(headerStream.GetBuffer(), 0, (int) headerStream.Length, SocketFlags.None);

			var buffer = new byte[65535];
			using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
			{
				int bytesRead = fs.Read(buffer, 0, buffer.Length);
				while (bytesRead > 0)
				{
					_socket.Send(buffer, 0, bytesRead, SocketFlags.None);
					bytesRead = fs.Read(buffer, 0, buffer.Length);
				}
			}
		}

		#region Nested type: FileHeader

		private class FileHeader
		{
			/// <summary>
			/// Gets or sets file name.
			/// </summary>
			public string FileName { get; set; }

			/// <summary>
			/// Gets or sets file size.
			/// </summary>
			public int Size { get; set; }

			/// <summary>
			/// Gets or sets when file was last modified.
			/// </summary>
			public DateTime ModifiedAt { get; set; }
		}

		#endregion
	}

	public class FileEventArgs : EventArgs
	{
		public FileEventArgs(string fileName)
		{
			
		}
	}
}